package edu.hadassah.prettyprint.parser;

import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import edu.hadassah.prettyprint.doc.xml.Tag;
import edu.hadassah.prettyprint.doc.xml.XMLDocument;
import edu.hadassah.prettyprint.doc.xml.XmlDocumentFactory;

public class XMLParser implements Parser {
	private static enum STATE {
		START_TAG, END_TAG, START_COMMENT
	};

	private STATE currentState;
	private final StringBuilder currentChunk;
	private XMLDocument currentDoc;
	private int parserDepth;
	private final Map<Integer, List<Tag>> parents;

	private final XmlDocumentFactory factory = new XmlDocumentFactory();
	
	public XMLParser() {
		parserDepth = -1;
		currentDoc = null;
		parents = new HashMap<Integer, List<Tag>>();
		this.currentChunk = new StringBuilder();
	}

	public XMLDocument parse(Reader in) throws ParserException, IOException {

		int intch;

		while ((intch = in.read()) != -1) {
			char ch = (char) intch;

			if (ch == '<') {
				if (this.currentChunk.length() > 0) {
					text(this.currentChunk.toString());
					this.currentChunk.delete(0, this.currentChunk.length());
				}

				if ((intch = in.read()) != -1) {
					ch = (char) intch;
					if (ch == '/') {
						this.currentState = STATE.END_TAG;
					} else if (ch == '!') {
						this.currentState = STATE.START_COMMENT;
					} else {
						this.currentState = STATE.START_TAG;
						this.currentChunk.append(ch);
					}
				} else {
					throw new ParserException("Unexpected end of file");
				}

				while ((intch = in.read()) != -1) {
					ch = (char) intch;
					if (ch == '>')
						break;
					this.currentChunk.append(ch);
				}

				if (this.currentState == STATE.START_TAG
						|| this.currentState == STATE.END_TAG) {
					parseTag();
				} else if (this.currentState == STATE.START_COMMENT) {
					comment(this.currentChunk.toString());
				}

				this.currentChunk.delete(0, this.currentChunk.length());
			} else {
				this.currentChunk.append(ch);
			}
		}

		return this.currentDoc;
	}

	private void parseTag() throws ParserException {
		if (this.currentState == STATE.START_TAG) {
			this.parserDepth++;
			String tagString = this.currentChunk.toString();
			String tagName = parseTagName(tagString);
			Map<String, String> tagAttributes = parseAttributes(tagString);
			startTag(tagName, tagAttributes);
		} else {
			this.parserDepth--;
			endTag(this.currentChunk.toString());
		}
	}

	/**
	 * @param tagString
	 * @return
	 */
	private Map<String, String> parseAttributes(String tagString) {
		int indexOfTagNameEnd = tagString.indexOf(' ');
		String attributesString = tagString.substring(indexOfTagNameEnd+1, tagString.length());
		String[] nameAndAttributes = attributesString.split("\\s*?=\\s*?\"|\"\\s|\"");
		Map<String, String> tagAttributes = new HashMap<String, String>();
		for (int i = 0; i < nameAndAttributes.length-1; i+=2) {
			tagAttributes.put(nameAndAttributes[i], nameAndAttributes[i+1]);
		}
		return tagAttributes;
	}

	/**
	 * @param tagString
	 * @return
	 */
	private String parseTagName(String tagString) {
		int indexOfSpace = tagString.indexOf(' ');
		if ( indexOfSpace < 0){
			return tagString;
		}
		return tagString.substring(0, indexOfSpace);
	}

	private void startTag(String name, Map<String, String> attributes)
			throws ParserException {
		Tag tag = null;
		if (this.parserDepth == 0) {
			if (this.currentDoc != null) {
				throw new ParserException("Multiple root tags are not allowed.");
			}
			tag = factory.createTag(name, null);
			this.currentDoc = factory.createDocument(tag);
		} else {
			List<Tag> list = this.parents.get(this.parserDepth - 1);
			Tag parent = list.get(list.size() - 1);
			tag = factory.createTag(name, parent);
			parent.addChild(tag);

		}

		List<Tag> list = this.parents.get(this.parserDepth);
		if (list == null) {
			list = new ArrayList<Tag>();
			this.parents.put(this.parserDepth, list);
		}
		list.add(tag);

		for (String aName : attributes.keySet()) {
			String aValue = attributes.get(aName);
			tag.addAttribute(factory.createAttribute(aName, aValue, tag));
		}
	}

	private void endTag(String name) {

	}

	private void text(String str) {
		if (str.trim().length() > 0) {
			List<Tag> list = this.parents.get(this.parserDepth);
			Tag parent = list.get(list.size() - 1);
			parent.addChild(factory.createText(str, parent));
		}

	}

	private void comment(String str) throws ParserException {

		if (!str.substring(0, 2).equals("--")
				|| !str.substring(str.length() - 2).equals("--")) {
			throw new ParserException("Invalid comment");
		}
		String comment = str.substring(2, str.length() - 2).trim();
		List<Tag> list = this.parents.get(this.parserDepth);
		Tag parent = list.get(list.size() - 1);
		parent.addChild(factory.createComment(comment, parent));
	}
}
